#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/SAX2.h>
#include <string.h>
#include <assert.h>
#include <glib.h>
#include <ctype.h>
#include <stdarg.h>

#include "server.h"
#include "sax.h"

extern MYSQL conn;

enum inside {
	IN_RULE = 1,
	IN_NAME = 2,
	IN_GAMETYPE = 4,
	IN_MAXPLAYERS = 8,
	IN_NUMPLAYERS = 16
};

struct saxContext {
	struct server *s;
	char ruleName[255];
	int inWhat;
	int ignoreServer;
	const char *game;
};

void _fatalErrror(void *c, const char *msg, ...)
{
	va_list ap;
	va_start(ap, msg);
	fprintf(stderr, msg, ap);
	va_end(ap);
}

void _errror(void *c, const char *msg, ...)
{
	va_list ap;
	va_start(ap, msg);
	fprintf(stderr, msg, ap);
	va_end(ap);
}

void _warning(void *c, const char *msg, ...)
{
	va_list ap;
	va_start(ap, msg);
	fprintf(stderr, msg, ap);
	va_end(ap);
}

void _characters(void *c, const xmlChar *value, int len)
{
	struct saxContext *ctx = c;
	if(ctx->inWhat & IN_RULE && value[0] != '\n') {
		if(!strcasecmp("bots", ctx->ruleName) && !ctx->s->bots) {
			char val[256];
			memset(val, 0, sizeof(val));
			strncpy(val, value, MIN(len, 255));
			if(isdigit(val[0])) {
				ctx->s->bots = atoi(val);
			}
		} else if(!strcasecmp("dedicated", ctx->ruleName) && !ctx->s->dedicated) {
			char val[256];
			memset(val, 0, sizeof(val));
			strncpy(val, value, MIN(len, 255));
			if(isdigit(val[0])) {
				ctx->s->dedicated = atoi(val);
			}
		} else if(!strcasecmp("secure", ctx->ruleName) && !ctx->s->secure) {
			char val[256];
			memset(val, 0, sizeof(val));
			strncpy(val, value, MIN(len, 255));
			if(isdigit(val[0])) {
				ctx->s->secure = atoi(val);
			}
		} else if(!strcasecmp("sv_os", ctx->ruleName)) {
			strncpy(ctx->s->os, value, MIN(len,31));
		}
		ctx->ruleName[0] = '\0';
	} else if(ctx->inWhat & IN_NAME && value[0] != '\n') {
		strncpy(ctx->s->name, value, MIN(len, 255));
	} else if(ctx->inWhat & IN_GAMETYPE && value[0] != '\n') {
		strncpy(ctx->s->gametype, value, MIN(len, 255));
	} else if(ctx->inWhat & IN_MAXPLAYERS && value[0] != '\n') {
		char buf[255] = "";
		memset(buf, 0, sizeof(buf));
		strncpy(buf, value, MIN(len, 255));
		ctx->s->maxplayers = atoi(buf);
	} else if(ctx->inWhat & IN_NUMPLAYERS && value[0] != '\n') {
		char buf[255] = "";
		memset(buf, 0, sizeof(buf));
		strncpy(buf, value, MIN(len, 255));
		ctx->s->numplayers = atoi(buf);
	}
}

void _endElement(void *c, const xmlChar *name)
{
	struct saxContext *ctx = c;
	if(!strcasecmp("server", name) && ctx->s) {
		if(!ctx->ignoreServer)
			update_server(&conn, ctx->s);
		if(ctx->s)
			g_free(ctx->s);
		ctx->s = NULL;
		ctx->ignoreServer = 0;
	} else if(!strcasecmp("rules", name)) {
		ctx->inWhat = ctx->inWhat & ~IN_RULE;
	} else if(!strcasecmp("name", name)) {
		ctx->inWhat = ctx->inWhat & ~IN_NAME;
	} else if(!strcasecmp("gametype", name)) {
		ctx->inWhat = ctx->inWhat & ~IN_GAMETYPE;
	} else if(!strcasecmp("numplayers", name)) {
		ctx->inWhat = ctx->inWhat & ~IN_NUMPLAYERS;
	} else if(!strcasecmp("maxplayers", name)) {
		ctx->inWhat = ctx->inWhat & ~IN_MAXPLAYERS;
	}

}

void _startElement(void *c, const xmlChar *name, const xmlChar **atts)
{
	struct saxContext *ctx = c;
	int i;
	if(!strcmp("server", name)) {
		ctx->s = g_malloc0(sizeof(struct server));
		strncpy(ctx->s->game, ctx->game, 16);
		for(i=0;atts && atts[i];i+=2) {
			if(!strcasecmp("type", atts[i]) && strcasecmp("HLA2S", atts[i+1])) {
				//fprintf(stderr, "ignoring server: type=%s\n", atts[i+1]);
				ctx->ignoreServer = 1;
			} else if(!strcasecmp("address", atts[i])) {
				char *addr = g_strdup(atts[i+1]);
				char *p;
				ctx->s->port = 27015;
				if((p = strchr(addr, ':'))) {
					*p = '\0';
					p++;
					ctx->s->port = atoi(p);
				}
				strncpy(ctx->s->ip, addr, 16);
				g_free(addr);
			}
		}
	} else if(!strcasecmp("rule", name)) {
		for(i=0;atts && atts[i];i+=2) {
			if(!strcasecmp("name", atts[i])) {
				strncpy(ctx->ruleName, atts[i+1], 255);
				break;
			}
		}
		ctx->inWhat = ctx->inWhat | IN_RULE;
	} else if(!strcasecmp("name", name)) {
		ctx->inWhat = ctx->inWhat | IN_NAME;
	} else if(!strcasecmp("gametype", name)) {
		ctx->inWhat = ctx->inWhat | IN_GAMETYPE;
	} else if(!strcasecmp("numplayers", name)) {
		ctx->inWhat = ctx->inWhat | IN_NUMPLAYERS;
	} else if(!strcasecmp("maxplayers", name)) {
		ctx->inWhat = ctx->inWhat | IN_MAXPLAYERS;
	}
}

void sax_parse_xmlfile(FILE *fp, const char *game)
{
	char buf[BUFSIZ] = "";
	xmlParserCtxtPtr ctxt;
	xmlDocPtr doc;
	size_t xmlsize;
	size_t len;
	int res;
	struct saxContext ctx = {
		.game = game
	};
#ifndef NDEBUG
	FILE *dbg;
	char dbgname[128];
#endif
	xmlSAXHandler hdlr = {};

	hdlr.startElement = _startElement;
	hdlr.endElement = _endElement;
	hdlr.cdataBlock = _characters;
	hdlr.characters = _characters;
	hdlr.warning = _warning;
	hdlr.error = _errror;
	hdlr.fatalError = _fatalErrror;

	/*if(!fgets(buf, BUFSIZ, fp)) {
		abort();
	}*/
	len = fread(buf,1, 4, fp);
	ctxt = xmlCreatePushParserCtxt(&hdlr, &ctx, buf, len, game);
#ifndef NDEBUG
	snprintf(dbgname, sizeof(dbgname), "%s.xml", game);
	dbg = fopen(dbgname, "w+");
	assert(dbg);
	fwrite(buf, 1, len, dbg);
#endif
	while((len = fread(buf, 1, BUFSIZ, fp))) {
	//while(fgets(buf, BUFSIZ, fp)) {
		int i;
#ifndef NDEBUG
		//fprintf(dbg, "%s", buf);
		fwrite(buf, 1, len, dbg);
#endif
		//len  = strlen(buf);
		for(i=0;i<len;i++) {
			if(buf[i]<20) {
				buf[i] = ' ';
			}
		}
		xmlParseChunk(ctxt, buf, len, 0);
		xmlsize+=len;
	}
#ifndef NDEBUG
	fclose(dbg);
#endif
	xmlParseChunk(ctxt, buf, 0, 1);
	res = ctxt->wellFormed;
	doc = ctxt->myDoc;
	xmlFreeParserCtxt(ctxt);
	xmlFreeDoc(doc);
}
